﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using System.Xml;

namespace ScreeningWebMvc.Helpers
{
    public class DataContractJsonValueProviderFactory : ValueProviderFactory
    {
        public IEnumerable<Type> KnownTypes { get; set; }
        
        private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
        {
            var dictionary = value as IDictionary<string, object>;

            if (dictionary != null)
            {
                foreach (var kvp in dictionary)
                {
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, kvp.Key), kvp.Value);
                }
            }
            else
            {
                var list = value as IList;
                if (list != null)
                {
                    for (var i = 0; i < list.Count; i++)
                    {
                        AddToBackingStore(backingStore, MakeArrayKey(prefix, i), list[i]);
                    }
                }
                else
                {
                    backingStore[prefix] = value;
                }
            }
        }

        private object GetDeserializedJson(ControllerContext controllerContext)
        {
            if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }

            controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
            var json = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();

            if(String.IsNullOrEmpty(json))
            {
                return null;
            }

            var serializer = new DataContractJsonSerializer(typeof(JsonKeyStringCollection), KnownTypes);

            controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
            var obj = (JsonKeyStringCollection) serializer.ReadObject(controllerContext.HttpContext.Request.InputStream);

            return obj.Collection;
        }

        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
            object deserializedJson = GetDeserializedJson(controllerContext);

            if (deserializedJson == null)
            {
                return null;
            }

            var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            AddToBackingStore(backingStore, string.Empty, deserializedJson);

            return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
        }

        private static string MakeArrayKey(string prefix, int index)
        {
            return (prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]");
        }

        private static string MakePropertyKey(string prefix, string propertyName)
        {
            if (!string.IsNullOrEmpty(prefix))
            {
                return (prefix + "." + propertyName);
            }

            return propertyName;
        }

        [Serializable]
        private class JsonKeyStringCollection : ISerializable
        {
            public Dictionary<string, object> Collection { get; set; }

            public JsonKeyStringCollection()
            {
                Collection = new Dictionary<string, object>();
            }

            protected JsonKeyStringCollection(SerializationInfo info, StreamingContext context) : this()
            {
                foreach (var entry in info)
                {
                    Collection.Add(entry.Name, entry.Value);
                }
            }

            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                foreach (var kvp in Collection)
                {
                    info.AddValue(kvp.Key, kvp.Value);
                }
            }
        }
    }
}